﻿using System;
using MovieFactory.Core.Primitives;

namespace MovieFactory.Sandbox.Surface
{

    public class DistrubutionBsdf : SurfaceBsdf
    {
        protected MicrofacetDistribution distr;

        protected IFresnelTerm fresnel;

        protected RgbSpectrum R0;

        public DistrubutionBsdf(MicrofacetDistribution dist,IFresnelTerm f, RgbSpectrum r0)
        {
            //Type = Reflection | Glossy;
            this.R0 = r0;
            this.distr = dist;
            this.fresnel = f;
        }

        public DistrubutionBsdf(float e, RgbSpectrum r0) : this(new Blinn(e), new FresnelNoOP(), r0) {}

        public static float G(ref Normal N, ref Vector wo, ref Vector wi, ref Vector wh)
        {
            float NdotWh = Vector.AbsDot(ref N, ref wh);
            float NdotWo = Vector.AbsDot(ref N, wo);
            float NdotWi = Vector.AbsDot(ref N, wi);
            float WOdotWh = Vector.AbsDot( ref wo, ref wh);
            return Math.Min(1f, Math.Min(2f * NdotWh * NdotWo / WOdotWh, 2f * NdotWh * NdotWi / WOdotWh));
        } 

        public override RgbSpectrum f(ref Vector wo, ref Vector wi, ref Normal N)
        {
            float cosThetaO = Vector.AbsDot(ref N, ref wo);
                //AbsCosTheta(wo);
            float cosThetaI = Vector.AbsDot(ref N, ref wi);
                //AbsCosTheta(wi);
            if (cosThetaI == 0f || cosThetaO == 0f) return new RgbSpectrum(0f);
            Vector wh = wi + wo;
            if (wh.IsZero()) return new RgbSpectrum(0f);
            wh = wh.Normalize();
            float cosThetaH = Vector.Dot(ref wi, ref wh);
            var F = fresnel.Evaluate(cosThetaH);

            return R0 * distr.D(ref wh) * G(ref N, ref wo, ref wi, ref wh) * F /  (4f * cosThetaI * cosThetaO); 
        }

        public static bool SameHemisphere(ref Vector w, ref Vector wp)
        {
            return w.z * wp.z > 0f;
        }

        public override float Pdf(ref Vector wo, ref Vector wi, BxDFTypes bxDFType)
        {
            if(!SameHemisphere(ref wo, ref wi))
                return 0f;
            return distr.Pdf(ref wo, ref wi);
        }

        public override RgbSpectrum Sample_f(ref Vector wo, out Vector wi, ref Normal N, ref Normal shadeN, float u0, float u1, float u2, out float pdf, out bool specularBounce)
        {
            specularBounce = false;
            distr.Sample_f(ref wo, out wi, u1, u2, out pdf);
            if(!SameHemisphere(ref wo, ref wi))
                return RgbSpectrum.ZeroSpectrum();
            return f(ref wo, ref wi, ref N); 
        }

        public override bool IsDiffuse()
        {
            return true;
        }

        public override bool IsSpecular()
        {
            return false;
        }
    }
}